//
// Created by A4-28 on 2020/9/23.
//

#include <glad/glad.h>
#include "../camera/camera.h"
#include "../material/Shader.h"
#include "../utils/stb_image.h"
#include "../utils/Utils.h"
#include "../material/Material.h"
#include <iostream>

Utils *utilss = new Utils();
GlfwInit *init = new GlfwInit();
Camera camera1(
        glm::vec3(0, 0, 3.0f),
        glm::radians(0.0f),
        glm::radians(180.0f),
        glm::vec3(0, 1.0f, 0),
        0.0005,
        0.0005,
        45.0);

float cubeVertices[] = {
        // positions          // texture Coords
        -0.5f, -0.5f, -0.5f, 0.0f, 0.0f,
        0.5f, -0.5f, -0.5f, 1.0f, 0.0f,
        0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
        0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
        -0.5f, 0.5f, -0.5f, 0.0f, 1.0f,
        -0.5f, -0.5f, -0.5f, 0.0f, 0.0f,

        -0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
        0.5f, -0.5f, 0.5f, 1.0f, 0.0f,
        0.5f, 0.5f, 0.5f, 1.0f, 1.0f,
        0.5f, 0.5f, 0.5f, 1.0f, 1.0f,
        -0.5f, 0.5f, 0.5f, 0.0f, 1.0f,
        -0.5f, -0.5f, 0.5f, 0.0f, 0.0f,

        -0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
        -0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
        -0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
        -0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
        -0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
        -0.5f, 0.5f, 0.5f, 1.0f, 0.0f,

        0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
        0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
        0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
        0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
        0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
        0.5f, 0.5f, 0.5f, 1.0f, 0.0f,

        -0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
        0.5f, -0.5f, -0.5f, 1.0f, 1.0f,
        0.5f, -0.5f, 0.5f, 1.0f, 0.0f,
        0.5f, -0.5f, 0.5f, 1.0f, 0.0f,
        -0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
        -0.5f, -0.5f, -0.5f, 0.0f, 1.0f,

        -0.5f, 0.5f, -0.5f, 0.0f, 1.0f,
        0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
        0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
        0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
        -0.5f, 0.5f, 0.5f, 0.0f, 0.0f,
        -0.5f, 0.5f, -0.5f, 0.0f, 1.0f
};
float planeVertices[] = {
        // positions          // texture Coords (note we set these higher than 1 (together with GL_REPEAT as texture wrapping mode). this will cause the floor texture to repeat)
        5.0f, -0.5f, 5.0f, 2.0f, 0.0f,
        -5.0f, -0.5f, 5.0f, 0.0f, 0.0f,
        -5.0f, -0.5f, -5.0f, 0.0f, 2.0f,

        5.0f, -0.5f, 5.0f, 2.0f, 0.0f,
        -5.0f, -0.5f, -5.0f, 0.0f, 2.0f,
        5.0f, -0.5f, -5.0f, 2.0f, 2.0f
};

void camera1Move(GLFWwindow *w, double xPos, double yPos) {
    camera1.Movement(xPos, yPos);
}

void handleInput(GLFWwindow *w) {
    if (glfwGetKey(w, GLFW_KEY_ESCAPE) == GLFW_PRESS) {
        glfwSetWindowShouldClose(w, true);
    }

    if (glfwGetKey(w, GLFW_KEY_A) == GLFW_PRESS) {
        camera1.SpeedX = -1.0f;
    } else if (glfwGetKey(w, GLFW_KEY_D) == GLFW_PRESS) {
        camera1.SpeedX = 1.0f;
    } else {
        camera1.SpeedX = 0.0f;
    }

    if (glfwGetKey(w, GLFW_KEY_W) == GLFW_PRESS) {
        camera1.SpeedZ = 1.0f;
    } else if (glfwGetKey(w, GLFW_KEY_S) == GLFW_PRESS) {
        camera1.SpeedZ = -1.0f;
    } else {
        camera1.SpeedZ = 0.0f;
    }

    if (glfwGetKey(w, GLFW_KEY_Q) == GLFW_PRESS) {
        camera1.SpeedY = 1.0f;
    } else if (glfwGetKey(w, GLFW_KEY_E) == GLFW_PRESS) {
        camera1.SpeedY = -1.0f;
    } else {
        camera1.SpeedY = 0.0f;
    }
}

int main() {

    //初始化 glfw 版本以及core模式
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

    //创建窗口
    GLFWwindow *window = glfwCreateWindow(400, 400, "depthTest", NULL, NULL);
    if (window == NULL) {
        printf("create window error!!");
        glfwTerminate();
        return 1;
    }
    //context
    glfwMakeContextCurrent(window);
    //cursor 移动位置的回调
    glfwSetCursorPosCallback(window, camera1Move);
    glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
    //初始glad
    if (!gladLoadGLLoader((GLADloadproc) glfwGetProcAddress)) {
        printf("init glad error");
        glfwTerminate();
        return 1;
    }
    //视口设置
    glViewport(0, 0, 400, 400);
    //开启深度测试
    glEnable(GL_DEPTH_TEST);
    /**
     * 模板测试 片段着色器处理一个片段后,模板测试会开始执行. 可能会丢弃部分片段,保留下来得片段进入深度测试.
     * :::::每一个模板缓冲中，（通常）每个模板值(Stencil Value)是8位的。所以每个像素/片段一共能有256种不同的模板值
     *

    //开启模板缓冲
//    glEnable(GL_STENCIL_TEST);
     *  glStencilMask允许我们设置一个位掩码(Bitmask)，它会与将要写入缓冲的模板值进行与(AND)运算。
     *  默认情况下设置的位掩码所有位都为1，不影响输出，但如果我们将它设置为0x00，写入缓冲的所有模板值最后都会变成0
    //0xFF 保存原样
//    glStencilMask(0xFF);
    //0x00 每一位模板缓冲都会变成0，禁止写入
//    glStencilMask(0x00);
    //配置模板测试  参数 1:比较函数 2:比较值 3:掩码
//    glStencilFunc(GL_ALWAYS, 1, 0xFF);
     以怎样的形式更新缓冲。
     * 参数：
     * 1 sfail：模板测试失败时采取的行为。
     * 2 dpfail：模板测试通过，但深度测试失败时采取的行为。
     * 3 dppass：模板测试和深度测试都通过时采取的行为。
     * 行为	   描述
     *  GL_KEEP	    保持当前储存的模板值
     *  GL_ZERO	    将模板值设置为0
     *  GL_REPLACE	将模板值设置为glStencilFunc函数设置的ref值
     *  GL_INCR	    如果模板值小于最大值则将模板值加1
     *  GL_INCR_WRAP 与GL_INCR一样，但如果模板值超过了最大值则归零
     *  GL_DECR	    如果模板值大于最小值则将模板值减1
     *  GL_DECR_WRAP 与GL_DECR一样，但如果模板值小于0则将其设置为最大值
     *  GL_INVERT	按位翻转当前的模板缓冲值

//    glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
    //通过使用glStencilFunc和glStencilOp，我们可以精确地指定更新模板缓冲的时机与行为了，我们也可以指定什么时候该让模板缓冲通过，即什么时候片段需要被丢弃。
*/
//通过使用模板缓冲，我们可以根据场景中已绘制的其它物体的片段，来决定是否丢弃特定的片段。
    //默认值为1
    glEnable(GL_STENCIL_TEST);
    glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);//当模板测试和深度测试都通过时，将模板缓冲值设置为 1

#pragma region other
    Shader shader = Shader();
    unsigned int ID = shader.loadShader(utilss->GetResourceDir().append("\\resource\\stencil_test\\vertex.glsl"),
                                        utilss->GetResourceDir().append("\\resource\\stencil_test\\frag.glsl"));
    unsigned int ID_2 = shader.loadShader(utilss->GetResourceDir().append("\\resource\\stencil_test\\vertex.glsl"),
                                          utilss->GetResourceDir().append(
                                                  "\\resource\\stencil_test\\frag_single_color.glsl"));
    unsigned int cubeVAO, cubeVBO;
    glGenVertexArrays(1, &cubeVAO);
    glGenBuffers(1, &cubeVBO);
    glBindVertexArray(cubeVAO);
    glBindBuffer(GL_ARRAY_BUFFER, cubeVBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(cubeVertices), &cubeVertices, GL_STATIC_DRAW);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void *) 0);
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void *) (3 * sizeof(float)));
    glBindVertexArray(0);
    // plane VAO
    unsigned int planeVAO, planeVBO;
    glGenVertexArrays(1, &planeVAO);
    glGenBuffers(1, &planeVBO);
    glBindVertexArray(planeVAO);
    glBindBuffer(GL_ARRAY_BUFFER, planeVBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(planeVertices), &planeVertices, GL_STATIC_DRAW);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void *) 0);
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void *) (3 * sizeof(float)));
    glBindVertexArray(0);

    unsigned int a = utilss->loadTexture(utilss->GetResourceDir().append("\\resource\\marble.jpg").c_str());
    unsigned int b = utilss->loadTexture(utilss->GetResourceDir().append("\\resource\\metal.png").c_str());

    glUniform1i(glGetUniformLocation(ID, "ourTexcoord"), GL_TEXTURE0);

    glm::mat4 model;
    glm::mat4 view;
    glm::mat4 project;
    //渲染
    while (!glfwWindowShouldClose(window)) {
        handleInput(window);

        glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
        //清除颜色缓冲值  深度缓冲值 模板缓冲值
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

#pragma endregion

        model = glm::mat4(1.0f);
        view = camera1.GetViewMatrix();
        project = glm::perspective(glm::radians(camera1.FOV), (float) 800 / (float) 800, 0.1f, 100.0f);


        glUseProgram(ID);
        glUniformMatrix4fv(glGetUniformLocation(ID, "view"), 1, false, glm::value_ptr(view));
        glUniformMatrix4fv(glGetUniformLocation(ID, "project"), 1, false, glm::value_ptr(project));

        //关闭地面的 stencil buffer 写入
        glStencilMask(0x00);
        // floor
        glBindVertexArray(planeVAO);
        model = glm::mat4(1.0f);
        glBindTexture(GL_TEXTURE_2D, b);
        glUniformMatrix4fv(glGetUniformLocation(ID, "model"), 1, false, glm::value_ptr(model));
        glDrawArrays(GL_TRIANGLES, 0, 6);
        glBindVertexArray(0);


        //开启cube的 stencil buffer 写入
        //总是通过模板测试
        /**
         * 在深度测试和模板测试都通过时，模板值会被设置为1
         */
        glStencilFunc(GL_ALWAYS, 1, 0xFF);
        glStencilMask(0xFF);

        // cubes
        glBindVertexArray(cubeVAO);
        model = glm::mat4(1.0f);
        model = glm::translate(model, glm::vec3(-1.0f, 0.0f, -1.0f));
        glBindTexture(GL_TEXTURE_2D, a);
        glUniformMatrix4fv(glGetUniformLocation(ID, "model"), 1, false, glm::value_ptr(model));
        glDrawArrays(GL_TRIANGLES, 0, 36);
        model = glm::mat4(1.0f);
        model = glm::translate(model, glm::vec3(2.0f, 0.0f, 0.0f));
        glUniformMatrix4fv(glGetUniformLocation(ID, "model"), 1, false, glm::value_ptr(model));
        glDrawArrays(GL_TRIANGLES, 0, 36);


        glUseProgram(ID_2);
        glUniformMatrix4fv(glGetUniformLocation(ID_2, "view"), 1, false, glm::value_ptr(view));
        glUniformMatrix4fv(glGetUniformLocation(ID_2, "project"), 1, false, glm::value_ptr(project));


        //放大
        /**
         * 改变func 不等于的1的模板值通过测试，则放大物体减去原始物体，剩下的则是通过模板测试的片段，
         * 在对这些片段进行渲染。
         * 对于shader2 来说，丢弃等于1的片段。剩下的就是边框
         */
        glStencilFunc(GL_NOTEQUAL, 1, 0xFF);
        glStencilMask(0x00);
        float scale = 1.1;

        float time = glfwGetTime();
        glUniform3f(glGetUniformLocation(ID_2, "stencilColor"), glm::sin(time), glm::cos(time * 0.5),
                    glm::tan(time * 0.7));
        // cubes
        glBindVertexArray(cubeVAO);
        model = glm::mat4(1.0f);
        model = glm::translate(model, glm::vec3(-1.0f, 0.0f, -1.0f));
        model = glm::scale(model, glm::vec3(scale, scale, scale));
        glBindTexture(GL_TEXTURE_2D, a);
        glUniformMatrix4fv(glGetUniformLocation(ID_2, "model"), 1, false, glm::value_ptr(model));
        glDrawArrays(GL_TRIANGLES, 0, 36);
        model = glm::mat4(1.0f);
        model = glm::translate(model, glm::vec3(2.0f, 0.0f, 0.0f));
        model = glm::scale(model, glm::vec3(scale, scale, scale));
        glUniformMatrix4fv(glGetUniformLocation(ID_2, "model"), 1, false, glm::value_ptr(model));
        glDrawArrays(GL_TRIANGLES, 0, 36);

        /**
         * 将那些丢弃片段的模板值重新写入,不然对下一帧来说,他还是被丢弃的.
         */
        //总是通过测试，同时模板值设置为 0
        glStencilFunc(GL_ALWAYS, 0, 0xFF);
        //再开启模板之写入，写入丢弃片段的模板值 设置为0
        glStencilMask(0xFF);

        glfwPollEvents();
        glfwSwapBuffers(window);
        camera1.UpdateCameraPosition();
    }
    glfwTerminate();
    return 1;
}